import * as C from './const.js'
import {Coord} from './coord.js'

export class State {
  constructor() {
    this.buf = new Uint8Array(new ArrayBuffer(12))
    this.step = new Uint8Array(new ArrayBuffer(1))
    this.pre = null
  }

  set_coord(index, x, y) {
    let v = (x << 3) | y
    this.buf.fill(v, index, index + 1)
  }

  get_coord(index) {
    let v = this.buf[index]
    return new Coord(v >> 3, v & 7)
  }

  set_step(index, direction) {
    this.step.fill((index << 2) | direction, 0, 1)
  }

  get_step() {
    let v = this.step[0]
    return {index: v >> 2, direction: v & 3}
  }

  _deep_copy(src) {
    return new Uint8Array(src);
  }

  _check_1pos_free(coord) {
    let f1 = this.get_coord(C.T_FREE1)
    let f2 = this.get_coord(C.T_FREE2)
    if (f1.x == coord.x && f1.y == coord.y) {
      return {result: true, index: C.T_FREE1}
    }
    if (f2.x == coord.x && f2.y == coord.y) {
      return {result: true, index: C.T_FREE2}
    }
    return {result: false, index: -1}
  }

  _check_2pos_free(c1, c2) {
    let f1 = this.get_coord(C.T_FREE1)
    let f2 = this.get_coord(C.T_FREE2)
    if (f1.x == c1.x && f1.y == c1.y && f2.x == c2.x && f2.y == c2.y) {
      return true
    }
    if (f1.x == c2.x && f1.y == c2.y && f2.x == c1.x && f2.y == c1.y) {
      return true
    }
    return false
  }

  _get1_2next(item) {
    let result = new Array()
    let coord = this.get_coord(item)
    let x = coord.x
    let y = coord.y
    if (coord.y > 0) {
      let current = this._check_1pos_free(new Coord(x, y - 1))
      if (current.result) {
        let new_state = new State()
        new_state.set_step(item, C.UP)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y - 1)
        new_state.set_coord(current.index, x, y + 1)
        result.push(new_state)
      }
    }
    if (x + 1 < C.COL_NUM) {
      let check_result = this._check_2pos_free(new Coord(x + 1, y), new Coord(x + 1, y + 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.RIGHT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x + 1, y)
        new_state.set_coord(C.T_FREE1, x, y)
        new_state.set_coord(C.T_FREE2, x, y + 1)
        result.push(new_state)
        return result
      }
    }
    if (coord.y + 2 < C.ROW_NUM) {
      let check_result = this._check_1pos_free(new Coord(x, y + 2))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.DOWN)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y + 1)
        new_state.set_coord(check_result.index, x, y)
        result.push(new_state)
      }
    }
    if (coord.x - 1 >= 0) {
      let check_result = this._check_2pos_free(new Coord(x - 1, y), new Coord(x - 1, y + 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.LEFT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x - 1, y)
        new_state.set_coord(C.T_FREE1, x, y)
        new_state.set_coord(C.T_FREE2, x, y + 1)
        result.push(new_state)
        return result
      }
    }
    return result
  }

  _get2_1next(item) {
    let result = new Array()
    let coord = this.get_coord(item)
    let x = coord.x
    let y = coord.y
    if (coord.y - 1 >= 0) {
      let check_result = this._check_2pos_free(new Coord(x, y - 1), new Coord(x + 1, y - 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.UP)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y - 1)
        new_state.set_coord(C.T_FREE1, x, y)
        new_state.set_coord(C.T_FREE2, x + 1, y)
        result.push(new_state)
        return result
      }
    }
    if (x + 2 < C.COL_NUM) {
      let check_result = this._check_1pos_free(new Coord(x + 2, y))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.RIGHT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x + 1, y)
        new_state.set_coord(check_result.index, x, y)
        result.push(new_state)
      }
    }
    if (y + 1 < C.ROW_NUM) {
      let check_result = this._check_2pos_free(new Coord(x, y + 1), new Coord(x + 1, y + 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.DOWN)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y + 1)
        new_state.set_coord(C.T_FREE1, x, y)
        new_state.set_coord(C.T_FREE2, x + 1, y)
        result.push(new_state)
        return result
      }
    }
    if (x - 1 >= 0) {
      let check_result = this._check_1pos_free(new Coord(x - 1, y))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.LEFT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x - 1, y)
        new_state.set_coord(check_result.index, x + 1, y)
        result.push(new_state)
      }
    }
    return result
  }

  _get1_1next(item) {
    let result = new Array()
    let coord = this.get_coord(item)
    let x = coord.x
    let y = coord.y
    if (y - 1 >= 0) {
      let check_result = this._check_1pos_free(new Coord(x, y - 1))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.UP)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y - 1)
        new_state.set_coord(check_result.index, x, y)
        result.push(new_state)
      }
    }
    if (x + 1 < C.COL_NUM) {
      let check_result = this._check_1pos_free(new Coord(x + 1, y))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.RIGHT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x + 1, y)
        new_state.set_coord(check_result.index, x, y)
        result.push(new_state)
      }
    }
    if (y + 1 < C.ROW_NUM) {
      let check_result = this._check_1pos_free(new Coord(x, y + 1))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.DOWN)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y + 1)
        new_state.set_coord(check_result.index, x, y)
        result.push(new_state)
      }
    }
    if (x - 1 >= 0) {
      let check_result = this._check_1pos_free(new Coord(x - 1, y))
      if (check_result.result) {
        let new_state = new State()
        new_state.set_step(item, C.LEFT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x - 1, y)
        new_state.set_coord(check_result.index, x, y)
        result.push(new_state)
      }
    }
    return result
  }

  _get2_2next(item) {
    let result = new Array()
    let coord = this.get_coord(item)
    let x = coord.x
    let y = coord.y
    if (y - 1 >= 0) {
      let check_result = this._check_2pos_free(new Coord(x, y - 1), new Coord(x + 1, y - 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.UP)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y - 1)
        new_state.set_coord(C.T_FREE1, x, y + 1)
        new_state.set_coord(C.T_FREE2, x + 1, y + 1)
        result.push(new_state)
        return result
      }
    }
    if (x + 2 < C.COL_NUM) {
      let check_result = this._check_2pos_free(new Coord(x + 2, y), new Coord(x + 2, y + 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.RIGHT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x + 1, y)
        new_state.set_coord(C.T_FREE1, x, y)
        new_state.set_coord(C.T_FREE2, x, y + 1)
        result.push(new_state)
        return result
      }
    }
    if (y + 2 < C.ROW_NUM) {
      let check_result = this._check_2pos_free(new Coord(x, y + 2), new Coord(x + 1, y + 2))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.DOWN)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x, y + 1)
        new_state.set_coord(C.T_FREE1, x, y)
        new_state.set_coord(C.T_FREE2, x + 1, y)
        result.push(new_state)
        return result
      }
    }
    if (x - 1 >= 0) {
      let check_result = this._check_2pos_free(new Coord(x - 1, y), new Coord(x - 1, y + 1))
      if (check_result) {
        let new_state = new State()
        new_state.set_step(item, C.LEFT)
        new_state.pre = this
        new_state.buf = new Uint8Array(this.buf)
        new_state.set_coord(item, x - 1, y)
        new_state.set_coord(C.T_FREE1, x + 1, y)
        new_state.set_coord(C.T_FREE2, x + 1, y + 1)
        result.push(new_state)
        return result
      }
    }
    return result
  }

  _update_key(key, x , y, t) {
    let pos = C.COL_NUM * y + x
    if (pos % 2 == 0) {
      let index = pos / 2
      let current = key[index]
      let new_value = ((current >> 4) << 4) | t
      key.fill(new_value, index, index + 1)
    } else {
      let index = (pos - 1) / 2
      let current = key[index]
      let new_value = (current & 15) | (t << 4)
      key.fill(new_value, index, index + 1)
    }
  }

  key() {
    let key = new Uint8Array(new ArrayBuffer(10))
    for (let item of C.ALL_LIST) {
      let coord = this.get_coord(item)
      if (item == C.T_MA || item == C.T_HUANG || item == C.T_ZHAO || item == C.T_ZHANG) {
        this._update_key(key, coord.x, coord.y, C.TYPE_1_2)
        this._update_key(key, coord.x, coord.y + 1, C.TYPE_1_2)
      } else if (item == C.T_BING1 || item == C.T_BING2 || item == C.T_BING3 || item == C.T_BING4) {
        this._update_key(key, coord.x, coord.y, C.TYPE_1_1)
      } else if (item == C.T_GUAN) {
        this._update_key(key, coord.x, coord.y, C.TYPE_2_1)
        this._update_key(key, coord.x + 1, coord.y, C.TYPE_2_1)
      } else if (item == C.T_CAO) {
        this._update_key(key, coord.x, coord.y, C.TYPE_2_2)
        this._update_key(key, coord.x + 1, coord.y, C.TYPE_2_2)
        this._update_key(key, coord.x, coord.y + 1, C.TYPE_2_2)
        this._update_key(key, coord.x + 1, coord.y + 1, C.TYPE_2_2)
      } else {
        this._update_key(key, coord.x, coord.y, C.TYPE_FREE)
      }
    }
    return key.toString()
  }

  next() {
    let result = new Array()
    for (let item of C.CHESS_LIST) {
      if (item == C.T_MA || item == C.T_HUANG || item == C.T_ZHAO || item == C.T_ZHANG) {
        let tmp = this._get1_2next(item)
        if (tmp.length > 0) {
          result = result.concat(tmp)
        }
      } else if (item == C.T_BING1 || item == C.T_BING2 || item == C.T_BING3 || item == C.T_BING4) {
        let tmp = this._get1_1next(item)
        if (tmp.length > 0) {
          result = result.concat(tmp)
        }
      } else if (item == C.T_GUAN) {
        let tmp = this._get2_1next(item)
        if (tmp.length > 0) {
          result = result.concat(tmp)
        }
      } else {
        let tmp = this._get2_2next(item)
        if (tmp.length > 0) {
          result = result.concat(tmp)
        }
      }
    }
    return result
  }

  success() {
    let coord = this.get_coord(C.T_CAO)
    return coord.x == 1 && coord.y == 3
  }
}